//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <kernio/io.h>
#include <core/portio.h>
#include <process/common.h>
#include <core/intrcommon.h>

// Variables

// the tick counter (how many ticks since startup, rolls over at 4 billion)
volatile uint32_t TickCounter = 0;

// Functions

// reprograms the PIT to fire at a different interval
void kChangeIntervalPIT( uint32_t hz )
{
    // calculate the divisor (this number is defined by the hardware spec)
    int16_t divisor = 1193180 / hz;

    // send the command
    outb( 0x43, 0x36 );

    // and then send the low and high bytes of the divisor
    outb( 0x40, divisor & 0xFF );
    outb( 0x40, divisor >> 8 );
}

// the timer callback
void kTimerCallback( struct stack_layout* UNUSED(stack) )
{
    // increment the tick counter
    TickCounter++;

	// stop relevant processes from sleeping
	struct pentry* ent;
	for(ent = p_sleepq; ent != 0; ent = ent->next)
	{
		if(ent && ent->me)
		{
			ent->me->sleep_curr_time++;
			if( (ent->me->sleep_curr_time + ent->me->sleep_start_time) >= ent->me->sleep_end_time )
			{
				// unlink from the sleep queue
				if(ent->prev)
					ent->prev->next = ent->next;
				else
					p_sleepq = ent->next;
				if(ent->next)
					ent->next->prev = ent->prev;

				// link into the ready queue
				if(p_readyq == 0)
				{
					p_readyq = ent;
					ent->prev = ent->next = 0;
				}
				else
				{
					p_readyq->prev = ent;
					ent->prev = 0;
					ent->next = p_readyq;
					p_readyq = ent;
				}
				ent->me->status = PSTATE_READY;
			}
		}
	}

	// print in the top right corner a little bit of memory usage information
	/*if( TickCounter % 1000 == 0 )
	{
		uint32_t oldx = kGetScreenX();
		uint32_t oldy = kGetScreenY();

		// move to the top right corner and print out memory usage information
		kSetScreenCoOrds( 80 - 20, 0 );

		printf( "free mem: %dkB", kGetAvailMem() / 1024 );

		// move back
		kSetScreenCoOrds( oldx, oldy );
	}*/

    // modify the current process timeslice
	if( --(currprocess->timeslice) == 0 || currprocess->status == PSTATE_INVALID )
		kSchedule(0);
}

// sets up the PIT
void kInitPIT()
{
    // install the callback
    kInstallCustomIntrHandler( INTERRUPT_FROM_IRQ( 0 ), kTimerCallback );

    // setup the PIT to work at 1000 hz
    kChangeIntervalPIT( 1000 );
}

// gets the PIT counter
inline uint32_t kGetPITCounter()
{
	return TickCounter;
}
